package hichecker.university;

import hichecker.controller.Config;
import hichecker.controller.MainController;
import hichecker.ui.components.icon_factory.IconFactory.IconType;
import hichecker.ui.custom_views.course.CourseSettings;
import hichecker.ui.custom_views.course.CourseView;
import hichecker.ui.custom_views.passing.PassingCondition.ConditionType;
import hichecker.ui.general_views.container_panel.content.ContentPanel;
import hichecker.university.jaxb.HashMapAdapter;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.UUID;
import java.util.Map.Entry;

import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlTransient;
import javax.xml.bind.annotation.XmlType;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;

/**
 * Class representing a single course.
 */
@XmlRootElement
@XmlType(propOrder = {"id", "pvls", "ptls", "courseGrades", "participants"})
public class Course extends UniversityPart {

	protected @XmlAttribute SemesterDate term;
	protected @XmlAttribute String lecturer;
	protected @XmlAttribute String name;
	protected @XmlAttribute int number;

	protected @XmlElement UUID id;
	protected @XmlElement(name = "pvl") List<PVL> pvls;
	protected @XmlElement(name = "ptl") List<PTL> ptls;
	protected @XmlJavaTypeAdapter(HashMapAdapter.class) HashMap<Student, Float> courseGrades;

	@XmlElementWrapper(name = "participants")
	@XmlElement(name = "student")
	protected List<Student> participants;

	protected List<ImportExportCourse> importCourses;


	/**
	 * Creates a course, generates a new UUID and sets it's name to
	 * {@link Config#NEW_VALUE_COURSE_NAME a new default value}
	 */
	public Course() {
		id = UUID.randomUUID();
		name = Config.NEW_VALUE_COURSE_NAME;
	}

	/**
	 * Creates a new course and copies all values out of the referenced course
	 *
	 * @param course
	 */
	public Course(Course course) {
		// Copy basic values
		id = course.getID();
		name = course.getName();
		lecturer = course.getLecturer();
		number = course.getNumber();
		term = new SemesterDate(course.getTerm());

		// Copy pvls
		pvls = new ArrayList<PVL>();
		for (PVL pvl : course.getPvls())
			pvls.add(new PVL(pvl));

		// Copy ptls
		ptls = new ArrayList<PTL>();
		for (PTL ptl : course.getPtls())
			ptls.add(new PTL(ptl));

		// Copy particpating students
		participants = new ArrayList<Student>();
		for (Student student : course.getParticipants())
			participants.add(new Student(student));

		// Copy grades
		courseGrades = new HashMap<Student, Float>();
		for (Entry<Student, Float> e : course.getGrades().entrySet())
			courseGrades.put(e.getKey(), e.getValue());
	}

	@Override
	@XmlTransient
	public UUID getID() {
		return id;
	}

	/**
	 * @param uuid
	 *            the unique id to set
	 */
	public void setID(UUID uuid) {
		this.id = uuid;
	}

	/**
	 * @return the number of the course
	 */
	@XmlTransient
	public int getNumber() {
		return number;
	}

	/**
	 * @param number
	 *            the number to set
	 */
	public void setNumber(int number) {
		this.number = number;
	}

	@Override
	@XmlTransient
	public String getName() {
		return name;
	}

	/**
	 * @param name the name to set
	 */
	public void setName(String name) {
		this.name = name;
	}

	/**
	 * @return the name of the lecturer
	 */
	@XmlTransient
	public String getLecturer() {
		return lecturer;
	}

	/**
	 * @param lecturer the name of the lecturer to set
	 */
	public void setLecturer(String lecturer) {
		this.lecturer = lecturer;
	}

	/**
	 * @return the term in which the course is hold
	 */
	@XmlTransient
	public SemesterDate getTerm() {
		return term = (term == null ? new SemesterDate("2010-10-01") : term);
	}

	/**
	 * @param term the term to set
	 */
	public void setTerm(SemesterDate term) {
		this.term = term;
	}

	/**
	 * @return a list of all {@link Student students} participating in this course
	 */
	@XmlTransient
	public List<Student> getParticipants() {
		return participants = (participants == null ? new ArrayList<Student>() : participants);
	}

	/**
	 * Indicates whether this course has a participating student which is equal to the referenced
	 * one
	 *
	 * @param id
	 * @return the student taken from this courses participants list, {@code null} otherwise
	 */
	public Student hasParticipant(Student student) {
		int index = participants.indexOf(student);
		return index == -1 ? null : participants.get(index);
	}

	/**
	 * @param participants the list of {@link Student students} participating in this course
	 */
	public void setParticipants(List<Student> participants) {
		this.participants = participants;
	}

	public void addParticipant(Student participant) {
		getParticipants().add(participant);
	}

	/**
	 * @return a list of all {@link PVL pvls} related to this course
	 */
	@XmlTransient
	public List<PVL> getPvls() {
		return pvls = (pvls == null ? new ArrayList<PVL>() : pvls);
	}

	/**
	 * @param pvls
	 *            the list of {@link PVL pvls} to set
	 */
	public void setPvls(List<PVL> pvls) {
		this.pvls = pvls;
	}

	/**
	 * @param pvl
	 *            the {@link PVL pvl} to be added
	 */
	public void addPvl(PVL pvl) {
		if (pvls == null)
			pvls = new ArrayList<PVL>();
		pvls.add(pvl);
	}

	/**
	 * @return a list of all {@link PTL ptls} related to this course
	 */
	@XmlTransient
	public List<PTL> getPtls() {
		return ptls = (ptls == null ? new ArrayList<PTL>() : ptls);
	}

	/**
	 * @param ptls
	 *            the list of {@link PTL ptls} to set
	 */
	public void setPtls(List<PTL> ptls) {
		this.ptls = ptls;
	}

	/**
	 * @param ptl
	 *            the {@link PTL ptl} to be added
	 */
	public void addPtl(PTL ptl) {
		if (ptls == null)
			ptls = new ArrayList<PTL>();
		ptls.add(ptl);
	}

	/**
	 * @return A list of all requirements related to this course in this order: PVL, PTL
	 */
	public List<Requirement> getRequirements() {
		List<Requirement> requirements = new ArrayList<Requirement>();

		requirements.addAll(getPvls());
		requirements.addAll(getPtls());

		return requirements;
	}

	/**
	 * @param requirement
	 *            the {@link Requirement requirement} to be added
	 */
	public void addRequirement(Requirement requirement) {
		if (requirement instanceof PVL)
			addPvl((PVL) requirement);
		else if (requirement instanceof PTL)
			addPtl((PTL) requirement);
	}

	/**
	 * Clears all requirements
	 */
	public void clearRequirements() {
		pvls = null;
		ptls = null;
	}

	/**
	 * TODO Überarbeiten
	 */
	public Series getTheOnlySeries() {
		List<Series> series = new ArrayList<Series>();

		for (Requirement requ : getRequirements()) {
			if (requ instanceof PVL)
				series.addAll(((PVL) requ).getSeries());
			if (requ instanceof PTL)
				series.addAll(((PTL) requ).getSeries());
		}
		if (series == null || series.size() > 1)
			try {
				throw new Exception("No or more than one serie available!");
			} catch (Exception e) {
				e.printStackTrace();
			}

		return series.get(0);
	}

	/**
	 * Searches for a series with the referenced id in all requirements and returns it if found.
	 *
	 * @param id
	 * @return {@code null} if the series can not be found
	 */
	public Series getSeriesByID(UUID id) {
		for (PVL pvl : getPvls())
			for(Series series : pvl.getSeries())
				if (series.getID().equals(id)) return series;
		for (PTL ptl : getPtls())
			for (Series series : ptl.getSeries())
				if (series.getID().equals(id)) return series;
		return null;
	}

	/**
	 * @return the grades of all students
	 */
	@XmlTransient
	public HashMap<Student, Float> getGrades() {
		return courseGrades = (courseGrades == null ? new HashMap<Student, Float>() : courseGrades);
	}

	/**
	 * Returns the grade a student gained. If the grade was manually set it is returned. Otherwise
	 * it will be calculated.
	 *
	 * @param student
	 * @return
	 */
	public float getGrade(Student student) {
		// Gucken, ob die Note bereits vorhanden ist
		if (courseGrades == null)
			courseGrades = new HashMap<Student, Float>();
		Float grade = courseGrades.get(student);
		if (grade != null)
			return grade <= 0 ? -1 : grade;

		// Note nicht vorhanden, also berechnen und dann speichern
		grade = 0f;
		for (PTL ptl : ptls)
			grade += ptl.getGrade(student) * ptl.getGradePercentage();
		courseGrades.put(student, grade);

		return grade <= 0 ? -1 : grade;
	}

	/**
	 * @param courseGrades the grades of all students to set
	 */
	public void setGrades(HashMap<Student, Float> courseGrades) {
		this.courseGrades = courseGrades;
	}

	/**
	 * Sets the grade of the referenced student
	 * @param student
	 * @param grade
	 */
	public void setGrade(Student student, float grade) {
		if (courseGrades == null) courseGrades = new HashMap<Student, Float>();
		courseGrades.put(student, grade);
	}

	public List<ImportExportCourse> getImportCourses() {
		return importCourses = (importCourses == null ? new ArrayList<ImportExportCourse>()
				: importCourses);
	}

	public void addImportCourse(ImportExportCourse course) {
		getImportCourses().add(course);
	}

	public void removeImportCourse(ImportExportCourse course) {
		getImportCourses().remove(course);
	}

	@Override
	public List<ConditionType> getPossibleConditions() {
		return null;
	}

	@Override
	public List<UniversityPart> getSubParts() {
		List<UniversityPart> parts = new ArrayList<UniversityPart>();
		parts.addAll(getPvls());
		parts.addAll(getPtls());
		parts.addAll(getImportCourses());
		return parts;
	}

	@Override
	public UniversityPart[] getPossibleSubPart() {
		return new UniversityPart[] {new PVL(), new PTL()};
	}

	@Override
	public UUID addSubPart(int type) {
		switch (type) {
		case 0:
			PVL pvl = new PVL();
			getPvls().add(pvl);
			return pvl.getID();
		case 1:
			PTL ptl = new PTL();
			getPtls().add(ptl);
			return ptl.getID();
		}
		return null;
	}

	@Override
	public void removeSubPart(UUID id) {
		getPvls().remove(new PVL(id));
		getPtls().remove(new PTL(id));
		getImportCourses().remove(new ImportExportCourse(false, id));
	}

	@Override
	public ContentPanel getRepresentation(boolean showConfig, MainController controller) {
		return showConfig ? new CourseSettings(this, controller) : new CourseView(this, controller);
	}

	@Override
	public IconType getIcon() {
		return IconType.PACKAGE;
	}

	@Override
	public IconType getAddIcon() {
		return null;
	}

	/**
	 * Two courses are equal if they have the same {@link #id}
	 */
	@Override
	public boolean equals(Object o) {
		if (!(o instanceof Course))
			return false;
		Course that = (Course) o;
		return that.id.equals(this.id);
	}
}
